const categoryNames = [
  'Art and design',
  'Living and dying',
  'Power and identity',
  'Religion and belief',
  'Trade and conflict'
];
let objects = null;
const scrollHelp = false;
const hoverInfoObject = null;
const yearIntervals = [
  {start: 2100, end: -100, yearLength: 0.3},
  {start: -100, end: -500, yearLength: 0.2},
  {start: -500, end: -1000, yearLength: 0.1},
  {start: -1000, end: -5000, yearLength: 0.025},
  {start: -5000, end: -5e6, yearLength: 0.0002}
];
const introLength = 14000;
var xorgen = new xor4096('heabllo.');
var loaderXorgen = new xor4096('alflog.');
var objectXorgen = new xor4096('hello.');
var $scope;

function InitWebGl () {
  var self = this;

  this.yearIntervals = yearIntervals;
  this.introTime = 0;
  this.cameraY = 0;
  this.cameraZMotion = 0;
  this.cameraZTarget = null;
  this.cameraXOffset = 0;
  this.cameraYOffset = 0.5;

  $scope = createScope(self);

  this.$scope = $scope;

  this.objectIndex = {};
  this.blobIndex = {};

  this.loadingPercentageMax = 0;

  var blobs = new THREE.Object3D();
  blobs.frustumCulled = false;

  var sticks = new THREE.Object3D();
  sticks.frustumCulled = false;

  this.objects = {
    blobs: blobs,
    sticks: sticks
  };


  this.yearZeroOffsets = [
    0, -50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50
  ];

  this.yearZeroOffsetIndex = 0;

  this.mapYearToLandscapeZ = function(year, offset) {
    year = year || 0;

    if (year === 0 && offset) {
      year += this.yearZeroOffsets[this.yearZeroOffsetIndex];
      this.yearZeroOffsetIndex = (this.yearZeroOffsetIndex + 1) % this.yearZeroOffsets.length;
    }

    var z = 0;
    for (var i=0; i < this.yearIntervals.length; i++) {
      var iv = this.yearIntervals[i];
      if (year > iv.start) {
        break;
      }
      var d = Math.min(iv.start - iv.end, iv.start - year);
      z += d * iv.yearLength;
    }
    return 1900*0.2 + 100*0.3 + 50*0.4 + 50*0.8 - z;
  };

  this.init(true);
}

InitWebGl.prototype.init = function(skipChecks) {

  this.now = (
    (window.performance && window.performance.now && window.performance.now.bind(window.performance)) ||
    Date.now.bind(Date)
  );

  this.lastFrameTime = this.now();

  // $scope.screenSizeWarning = false;

  if (!skipChecks && (window.innerWidth < 770 || /mobile|iPad|iPhone|Android/i.test(navigator.userAgent))) {
    // $scope.screenSizeWarning = true;
    return;
  }

  // $scope.webgl = Detector.webgl;
  if (!Detector.webgl){
    return;
  }

  if (!this.initDone) {
    var self = this;

    this.geographies = ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania'];
    this.geoLane = {};
    this.geographies.forEach(function(e,i) { self.geoLane[e] = i; });
    this.colors = [
      0xFFBA0A, 0xF14F11, 0x8DC73C, 0x0071BC, 0x9E005D
    ];

    var blobTextures = [];
    for (var i=0; i<16; i++) {
      var tex = new THREE.ImageUtils.loadTexture('http://localhost:5000/img/blob_'+ i +'.png', undefined, function() {
        self.loadingPercentageMax += 2;
      }, function() {
        self.loadingPercentageMax += 2;
      });
      tex.premultiplyAlpha = true;
      blobTextures.push(tex);
    }
    this.blobTextures = blobTextures;
    /**
     * 环境着色器
     * @type {THREE.ShaderMaterial}
     */
    this.landscapeShaderMaterial = landscapeShaderMaterial

    /**
     * 素材着色器
     * @type {THREE.ShaderMaterial}
     */
    this.canvasShaderMaterial = canvasShaderMaterial

    /**
     * 线条着色器
     * @type {THREE.ShaderMaterial}
     */
    this.lineShaderMaterial = lineShaderMaterial
  }

  // $scope.cameraMotionType = 'onlyTerrain';
  // $scope.cameraMotionType = 'resizing2DToolBar';
  // $scope.cameraMotionType = 'fixed2DToolBar';
  // $scope.cameraMotionType = '3DToolBar';

  // $scope.hoverInfoX = 0;
  // $scope.hoverInfoY = 0;

  // $scope.loadingPercentage = 0;

  if (!this.initDone) {
    this.initDone = true;

    this.initWebGL();
    this.initLandscape();
    setupEventListeners(this, $scope);
    window.onresize();

    fetch('http://localhost:5000/js/landscapeCoords.json').then(res => res.json()).then(function(object) {
      // console.log('js/landscapeCoords.json', object)
      initTerrain.call(self, object);
      self.loadingPercentageMax += 50;
    }, function(err) {
      console.log("Failed to load terrain geometry.")
    });

    this.tick = this.tick.bind(this);
    this.tick();
  }
};

InitWebGl.prototype.initWebGL = function() {
  var renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio || 1);

  this.lastFrameTime = this.now();
  this.frameElapsed = 0;

  var width = window.innerWidth;
  var height = window.innerHeight;

  renderer.setSize(width, height);

  renderer.setClearColor(0x66685D, 1.0);

  this.renderer = renderer;

  this.halfX = width / 2;
  this.halfY = height / 2;

  this.width = width;
  this.height = height;

  if (this.width > this.height) {
    this.cameraYOffset = 1;
  }

  renderer.domElement.id = 'main-canvas';
  document.body.appendChild(renderer.domElement);

  this.itemGeometry = new THREE.PlaneBufferGeometry(1, 1);
  this.initScene();

  return true;
};

InitWebGl.prototype.initScene = function() {
  var scene = new THREE.Scene();
  scene.frustumCulled = false;
  var camera = new THREE.PerspectiveCamera(50, this.width/this.height, 1, 3000);
  camera.position.z = 100;
  camera.position.y = 2;

  camera.target = new THREE.Vector3();
  camera.target.copy(camera.position);

  scene.fog = new THREE.Fog(0x66685D, 100, 400);

  window.camera = camera;
  scene.add(camera);

  this.labelScene = new THREE.Scene();
  this.labelScene.fog = scene.fog;

  this.scene = scene;
  this.camera = camera;
  this.cameraVelocity = new THREE.Vector3();


};

InitWebGl.prototype.render = function() {
  this.renderer.autoClear = false;
  this.renderer.clear();
  this.renderer.render(this.scene, this.camera);
  this.renderer.render(this.labelScene, this.camera);
};

InitWebGl.prototype.createObjects = function(objectArray) {

    var blobs = new THREE.Object3D();
    blobs.frustumCulled = false;

    var sticks = new THREE.Object3D();
    sticks.frustumCulled = false;

    this.objects = {
      blobs: blobs,
      sticks: sticks
    };

    var geographies = this.geographies;
    var geoLane = this.geoLane;
    var colors = this.colors;

    var blobLayoutGrid = {};
    var fitBlob = function(v) {
      var vx = Math.floor(v.x/2);
      var vy = Math.floor(v.y/2);
      var vz = Math.floor(v.z/2);
      var k = [vx, vy, vz].join(":");
      if (!blobLayoutGrid[k]) {
        for (var x = -1; x <= 1; x++) {
          for (var y = -1; y <= 1; y++) {
            for (var z = -1; z <= 1; z++) {
              k = [vx+x, vy+y, vz+z].join(":");
              blobLayoutGrid[k] = true;
            }
          }
        }

        return true;
      }
      return false;
    };
    var blobShift = function(v) {
      var i = 0;
      while (!fitBlob(v)) {
        if (i === 0) { v.x += 2; }
        else if (i === 1) { v.x += -4; }
        else if (i === 2) { v.x += 2; v.y += 2; }
        else if (i === 3) { v.x += 0; v.y += -4; }
        else if (i === 4) { v.x += 2; v.y += 0; }
        else if (i === 5) { v.x += -4; v.y += 0; }
        else if (i === 6) { v.x += 0; v.y += 4; }
        else if (i === 7) { v.x += 4; v.y += 0; }
        else if (i === 8) { v.z += 4; break; }
        i++;
      }
    };

    var clamp = function(x) {
      return x > 1 ? 1 : (x < 0 ? 0 : x);
    };

    var shaderMaterial = this.canvasShaderMaterial;

    objectArray = objectArray.filter(function(obj) {
      return geoLane[obj.cultureName] !== undefined;
    });

    for (var i=0; i<objectArray.length; i++) {
      var obj = objectArray[i];
      var lane = geoLane[obj.cultureName];

      // console.log(geoLane);
      // console.log(lane);

      var color = new THREE.Color( colors[lane] || 0x0 );
      obj.backgroundColor = color.getStyle();

      color.r = clamp(color.r * (1+(Math.random() - 0.5) * 0.2));
      color.g = clamp(color.g * (1+(Math.random() - 0.5) * 0.2));
      color.b = clamp(color.b * (1+(Math.random() - 0.5) * 0.2));

      var mat = shaderMaterial.clone();
      mat.color = color;
      mat.colorChangeSpeed = 0.25;
      mat.colorChangeDelay = Math.ceil(Math.random()*30);
      mat.uniforms = {
        map: {type: 't', value: this.blobTextures[i % this.blobTextures.length]},
        color: {type: 'v3', value: new THREE.Vector3(color.r, color.g, color.b)}
      };
      var blob = new THREE.Mesh(this.itemGeometry, mat);

      blob.rotation.z = Math.random()*Math.PI*2;

      blob.laneIndex = lane;

      blob.objectData = obj;
      this.blobIndex[obj.bullet] = blob;

      blob.connections = [];
      blob.frustumCulled = false;
      blob.sticksContainer = new THREE.Object3D();
      blob.sticksContainer.visible = false;
      blob.sticksContainer.frustumCulled = false;
      blob.sticksContainer.tick = this.scene.tick;
      blob.frustumCulled = false;
      blob.position.set(
        6*(6/5) * (lane-(geographies.length-1)/2)+4*(objectXorgen()-0.5),
        -10+objectXorgen()*5,
        this.mapYearToLandscapeZ(obj.dateCreated, true)
      );

      blobShift(blob.position);
      blob.originalPosition = blob.position.clone();

      blob.scale.multiplyScalar(1.5);
      blob.relativePos = new THREE.Vector3();
      blob.targetPos = new THREE.Vector3();
      blob.velocity = new THREE.Vector3();
      blob.floatVec = new THREE.Vector3();
      blob.posDelta = new THREE.Vector3();
      blob.tick = this.blobTick;

      blob.sticks = [];

      blob.addConnection = this.addBlobConnection;

      blob.hoverConnectionTarget = blob.clone();
      blob.hoverConnectionTarget.scale.set(0.01, 0.01, 0.01);
      blob.addConnection(blob.hoverConnectionTarget);

      blobs.add(blob);
    }

    return {blobs: blobs, sticks: sticks};
  };

InitWebGl.prototype.tick = function(t, el, secondTick) {

  var cameraMinZ = this.mapYearToLandscapeZ(-2e6) + 40;
  var cameraMaxZ = this.mapYearToLandscapeZ(2015) + 40;

  /*this.active = ($location.path().replace("/","") === '');
  if (!this.objects || !this.active) {
    this.renderer.clear();
    window.requestAnimationFrame(this.tick);
    return;
  }*/
  var scene = this.scene;
  var camera = this.camera;
  var introState = 3 // $scope.introState;
  var introTime = this.introTime;
  var cameraY = this.cameraY;
  var cameraZMotion = this.cameraZMotion;
  var cameraZTarget = this.cameraZTarget;

  if (this.introTime > 3000 && this.audioManager) {
    this.audioManager.start();
  }

  if (this.introTime < this.introLength + 16) {
    introTime = Math.max(0, this.introTime - (this.introLength - 3000));


    camera.position.y = Math.max(cameraY, cameraY + 40 * Math.pow(Math.max(0, 1-introTime/3000), 3.0));
    camera.position.z = (this.mapYearToLandscapeZ(2015)
      + 28
      + -800 * Math.max(0, 1-Math.pow(introTime/3000, 2))
    );

    camera.target.x = 0;
    camera.target.y = camera.position.y + 40 * (1-introTime/3000) - 10;
    camera.target.z = camera.position.z - 50;

    this.lines.position.z = this.camera.position.z;

    camera.lookAt(camera.target);

    if (introState >= 2) {
      this.introTime += 16;
    }

    if (this.introBlobs) {
      this.introBlobs.position.copy(camera.target);
      this.introBlobs.lookAt(camera.position);
    }
  }

  camera.position.z += cameraZMotion * 0.1;
  if (cameraZMotion > 0 && cameraZMotion < 20) {
    cameraZMotion += 0.25;
  } else if (cameraZMotion < 0 && cameraZMotion > -20) {
    cameraZMotion -= 0.25;
  }
  if (cameraZMotion === 0) {
    if (cameraZTarget && Math.abs(cameraZTarget-camera.position.z) < 0.001) {
      cameraZTarget = null;
    }
    if (cameraZTarget !== null) {
      camera.position.z += (cameraZTarget - camera.position.z) * 0.05;
    }
  }
  this.cameraZMotion = cameraZMotion;
  this.cameraZTarget = cameraZTarget;

  if (this.introTime >= this.introLength + 16) {
    this.linesPlane.visible = false;

    camera.position.z += this.cameraVelocity.z;

    if (Math.abs(this.cameraVelocity.z) < 1) {
      this.cameraVelocity.z *= 0.9;
    } else {
      this.cameraVelocity.z *= 0.95;
    }

    if (Math.abs(this.cameraVelocity.z) < 0.01) {
      this.cameraVelocity.z = 0;
    }

    this.cameraXOffset += this.cameraVelocity.x;
    if (this.cameraXOffset > 1) this.cameraXOffset = 1;
    if (this.cameraXOffset < -1) this.cameraXOffset = -1;
    this.cameraVelocity.x *= 0.9;

    camera.position.x += (this.cameraXOffset * 10 - camera.position.x) * 0.05;
    camera.position.y += (cameraY + 8 + this.cameraYOffset * 3 - camera.position.y) * 0.05;

    camera.target.x += (Math.sin(this.cameraXOffset * 0.5) * 30 - camera.target.x) * 0.1;
    camera.target.y += (cameraY + 8 - 20 + this.cameraYOffset * 5 - camera.target.y) * 0.1;
    camera.target.z = camera.position.z - Math.cos(this.cameraXOffset * 0.5) * 50;

    camera.lookAt(camera.target);

    camera.updateProjectionMatrix();
    camera.updateMatrix();

    this.labels.visible = true;
    this.toolBar3D.visible = true;
    this.labels.position.z = camera.position.z - 25;
    this.toolBar3D.position.z = camera.position.z - 25;

    this.lines.position.z = this.toolBar3D.position.z;

    if (camera.position.z > cameraMaxZ) {
      camera.position.z += (cameraMaxZ - camera.position.z) * 0.1;
    } else if (camera.position.z < cameraMinZ) {
      camera.position.z += (cameraMinZ - camera.position.z) * 0.1;
    }

    if (this.cameraZTarget > cameraMaxZ) {
      this.cameraZTarget += (cameraMaxZ - this.cameraZTarget) * 0.1;
    } else if (this.cameraZTarget < cameraMinZ) {
      this.cameraZTarget += (cameraMinZ - this.cameraZTarget) * 0.1;
    }

    /*if ($scope.hoverInfoObject) {
      var hoverInfoEl = document.getElementById('object-hover-info');
      scene.updateMatrixWorld();
      this.linesPlane.updateMatrixWorld();
      $scope.hoverInfoObject.hoverConnectionTarget.updateMatrixWorld();
      this.moveToScreenCoords( $scope.hoverInfoObject.hoverConnectionTarget, camera, hoverInfoEl.getBoundingClientRect() );
    }*/
  }

  this.landscapeShaderMaterial.uniforms.time.value += 0.016;

  // scene.tick();

  if (this.frameElapsed > 25 && (!secondTick || secondTick < 4)) {
    this.frameElapsed -= 25;
    this.tick(time, undefined, (secondTick || 0) + 1);
  } else {
    var time = this.now();
    this.frameElapsed = time - this.lastFrameTime;
    this.lastFrameTime = time;
    this.render();

    /*if ( this.findToolbarUnderEvent(this.mouseCurrent) || this.findObjectUnderEvent(this.mouseCurrent) ) {
      this.overlaysEl.style.cursor = 'pointer';
    } else {
      this.overlaysEl.style.cursor = 'default';
    }*/

    window.requestAnimationFrame(this.tick);
  }
};
/**
 * 初始化景观
 */
InitWebGl.prototype.initLandscape = function() {
  this.lanes = this.createLanes();
  this.dividers = this.lanes.dividers;
  this.lines = this.lanes.lines;
  this.linesPlane = this.lanes.linesPlane;
  this.scene.add(this.lanes);

  this.timeArrow = this.createTimeArrow();
  this.scene.add(this.timeArrow);
  var self = this;

  this.timeArrow.tick = function() {
    this.position.z = self.camera.position.z + this.zOffset;
  };

  Objects.get(function(objectArray, objectIndex) {

    var i = 0;
    self.objectIndex = objectIndex;
    self.objects = self.createObjects(objectArray);
    self.objects.blobs.tick = self.scene.tick;
    self.objects.sticks.tick = self.scene.tick;

    self.scene.add(self.objects.blobs, self.objects.sticks);

    var blobs = self.objects.blobs;

    // console.time('blobs');

    var processBlobs = function(i) {
      var startTime = Date.now();
      for (; Date.now()-startTime < 12 && i<blobs.children.length; i++) {
        var blob = blobs.children[i];
        var obj = blob.objectData;
        var c = obj.dateCreated;
        var bullet = obj.bullet;
        obj.categoryObjects.sort(function(a, b) {
          return Math.abs(a.dateCreated - c) - Math.abs(b.dateCreated - c);
        });
        for (var j=0; j<obj.categoryObjects.length; j++) {
          var related = self.blobIndex[obj.categoryObjects[j].bullet];
          if (related && related.objectData.bullet !== bullet) {
            blob.addConnection(related);
          }
        }
      }
      return i;
    }

    var ival = setInterval(function() {
      // console.log(i);
      i = processBlobs(i);
      if (i === blobs.children.length) {
        // console.log('done');
        clearInterval(ival);
        self.loadingPercentageMax += 10;
        // console.timeEnd('blobs');
        return;
      }
    }, 16);

  });

};

InitWebGl.prototype.createTimeArrow = function() {
  // 创建基础材质
  var lineMaterial = new THREE.LineBasicMaterial({ color: 0xCCCCCC, fog: true });
  // 创能几何体
  var upArrowGeo = new THREE.Geometry();
  //
  upArrowGeo.vertices.push(new THREE.Vector3(-1, 1, -1));
  upArrowGeo.vertices.push(new THREE.Vector3(0, 0, 0));
  upArrowGeo.vertices.push(new THREE.Vector3(1, -1, -1));

  var downArrowGeo = new THREE.Geometry();
  downArrowGeo.vertices.push(new THREE.Vector3(-1, 1, 1));
  downArrowGeo.vertices.push(new THREE.Vector3(0, 0, 0));
  downArrowGeo.vertices.push(new THREE.Vector3(1, -1, 1));

  var arrowLineGeo = new THREE.Geometry();
  arrowLineGeo.vertices.push(new THREE.Vector3(0,0,0));
  arrowLineGeo.vertices.push(new THREE.Vector3(0,0,1));

  var arrowLineSeparatorGeo = new THREE.Geometry();
  arrowLineSeparatorGeo.vertices.push(new THREE.Vector3(0,0,0));
  arrowLineSeparatorGeo.vertices.push(new THREE.Vector3(1,0,0));

  // 上下分离器
  var upSeparator = new THREE.Line(arrowLineSeparatorGeo, lineMaterial);
  var downSeparator = new THREE.Line(arrowLineSeparatorGeo, lineMaterial);

  // 上下箭头
  var upArrow = new THREE.Line(upArrowGeo, lineMaterial);
  var downArrow = new THREE.Line(downArrowGeo, lineMaterial);

  // 上下箭头
  var upArrowLine = new THREE.Line(arrowLineGeo, lineMaterial);
  var downArrowLine = new THREE.Line(arrowLineGeo, lineMaterial);

  var textCanvas = document.createElement('canvas');
  textCanvas.width = 128;
  textCanvas.height = 32;

  var ctx = textCanvas.getContext('2d');
  ctx.font = '30px Lato, Arial';
  ctx.fillStyle = '#FFFFFF';
  var textWidth = ctx.measureText("TIME").width;
  ctx.fillText("TIME", (textCanvas.width - textWidth) / 2, 28);

  var tex = new THREE.Texture(textCanvas);
  tex.needsUpdate = true;

  var mat = this.canvasShaderMaterial.clone();
  var color = new THREE.Color(0xCCCCCC);
  mat.uniforms = {
    map: {type: 't', value: tex},
    color: {type: 'v3', value: new THREE.Vector3(color.r, color.g, color.b)}
  };

  var text = new THREE.Mesh( new THREE.PlaneBufferGeometry(textCanvas.width/25, textCanvas.height/25), mat );
  text.position.set(64/25, 16/25, 0);
  text.rotation.x = -0.4;

  upSeparator.position.y = 16/25 - 16/25 * Math.cos(text.rotation.x);
  upSeparator.position.z = - 16/25 * Math.sin(text.rotation.x);
  downSeparator.position.y = 16/25 + 16/25 * Math.cos(text.rotation.x);
  downSeparator.position.z = + 16/25 * Math.sin(text.rotation.x);

  upSeparator.scale.set(128/25, 1, 1);
  downSeparator.scale.set(128/25, 1, 1);

  upArrowLine.position.set(64/25, upSeparator.position.y, upSeparator.position.z);
  upArrowLine.scale.set(1, 1, 20);
  downArrowLine.position.set(64/25, downSeparator.position.y, downSeparator.position.z);
  downArrowLine.scale.set(1, 1, -20);

  upArrow.position.copy(upArrowLine.position);
  upArrow.position.z += upArrowLine.scale.z;

  downArrow.position.copy(downArrowLine.position);
  downArrow.position.z += downArrowLine.scale.z;


  var timeLane = new THREE.Object3D();

  timeLane.position.set(-35, -5, 0);
  timeLane.zOffset = -60;

  timeLane.add(text);
  timeLane.add(upSeparator);
  timeLane.add(downSeparator);

  timeLane.add(upArrow);
  timeLane.add(upArrowLine);

  timeLane.add(downArrow);
  timeLane.add(downArrowLine);

  return timeLane;

}; // JWK4dGYgcwtEkEX
InitWebGl.prototype.makeLine = function(geo, mat) {
  var smat = this.lineShaderMaterial.clone();
  smat.uniforms =  {
    color: {type: 'v3', value: new THREE.Vector3(mat.color.r, mat.color.g, mat.color.b)}
  };
  return new THREE.Line(geo, smat);
};
InitWebGl.prototype.addBlobConnection = function(connection) {
  if (connection.objectData && this.objectData.bullet === connection.objectData.bullet) {
    // console.log("You're doing it wrong");
  }
  var stick = new THREE.Mesh(
    new THREE.BoxGeometry(1,1,1),
    new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors })
  );
  stick.frustumCulled = false;
  var stickContainer = new THREE.Object3D();
  stickContainer.tick = self.stickTick;
  stickContainer.stick = stick;
  stickContainer.start = this;
  stickContainer.end = connection;
  if (this.connections.length > 6) {
    var nearest = this.connections[1];
    var minLen = 1/0;
    for (var i=1; i<this.connections.length; i++) {
      var obj = this.connections[i];
      var len = obj.position.distanceTo(connection.position);
      if (minLen > len && obj.objectData.bullet !== connection.objectData.bullet) {
        minLen = len;
        nearest = obj;
      }
    }
    stickContainer.start = nearest;
  }
  this.connections.push(connection);
  var color1 = stickContainer.end.material.color.clone();
  var color2 = stickContainer.start.material.color.clone();
  stick.endColor = color1;
  stick.startColor = color2;
  var faceIndices = [ 'a', 'b', 'c' ];
  for ( var i = 0; i < stick.geometry.faces.length; i ++ ) {
    var f  = stick.geometry.faces[ i ];
    for( var j = 0; j < 3; j++ ) {
      var vertexIndex = f[ faceIndices[ j ] ];
      var p = stick.geometry.vertices[ vertexIndex ];
      f.vertexColors[ j ] = p.z < 0.0 ? color1 : color2;
    }
  }
  stick.geometry.colorsNeedUpdate = true;

  stickContainer.add(stick);

  this.sticks.push(stickContainer);
  this.sticksContainer.add(stickContainer);
};
InitWebGl.prototype.createLanes = function() {
  var lineGeometry = new THREE.Geometry();
  for (var i = 0; i <= 1500; i += 5) {
    lineGeometry.vertices.push(new THREE.Vector3( 0, 0, -i ));
  }

  var timelineGeometry = new THREE.Geometry();
  timelineGeometry.vertices.push(new THREE.Vector3( -1, 0, 0 ));
  timelineGeometry.vertices.push(new THREE.Vector3( 1, 0, 0 ));

  var lineMaterial = new THREE.LineBasicMaterial({ color: 0xCCCCCC, fog: true });

  var lanes = new THREE.Object3D();
  var lines = new THREE.Object3D();
  var dividers = new THREE.Object3D();
  var linesPlane = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(144, 144),
    new THREE.MeshBasicMaterial({ color: 0xff00ff })
  );
  linesPlane.material.side = THREE.DoubleSide;
  linesPlane.position.y = 0;
  linesPlane.position.z = 10;
  //linesPlane.rotation.x = Math.PI/2;
  linesPlane.visible = false;
  lines.add(linesPlane);

  lanes.dividers = dividers;
  lanes.add(dividers);
  lanes.linesPlane = linesPlane;

  lines.position.z = 1000;

  var formatNumber = function(n) {
    var s = n.toString();
    var ss = "";
    for (var i = 0; i < s.length; i++) {
      var j = s.length - 1 - i;
      ss = s.charAt(j) + ss;
      if (i > 0 && i % 3 === 2 && j > 0) {
        ss = "," + ss;
      }
    }
    return ss;
  };


  var itemLanes = this.geographies.length;

  var yearTickGeometry = new THREE.Geometry();

  var lastYearZ = null;

  var years = [
    -2e6,
    -1e6,
    -500e3,
    -5e3, -4e3, -3e3, -2e3, -1e3,
    -500, -400, -300, -200, -100,
    0,
    100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000
  ];

  for (var i=0; i<years.length; i++) {
    var year = years[i];
    var z = this.mapYearToLandscapeZ(year);

    var lineContainer = new THREE.Object3D();
    var line = this.makeLine( timelineGeometry, lineMaterial );
    line.scale.set(30, 1, 1);
    lineContainer.position.set( 0, -10, z );
    lineContainer.add(line);
    dividers.add(lineContainer);

    var textCanvas = document.createElement('canvas');
    textCanvas.width = 256;
    textCanvas.height = 32;

    var ctx = textCanvas.getContext('2d');
    ctx.font = 'bold 30px Lato, Arial';
    ctx.fillStyle = '#FFFFFF';
    ctx.fillText((year > 0) ? 'AD ' + year : ((year < 0) ? (year <= -10e3 ? formatNumber(-year) : -year) + ' BC' : 'BC / AD'), 2, 28);

    var tex = new THREE.Texture(textCanvas);
    tex.needsUpdate = true;

    var mat = this.canvasShaderMaterial.clone();
    var color = new THREE.Color(0xFFFFFF);
    mat.uniforms = {
      map: {type: 't', value: tex},
      color: {type: 'v3', value: new THREE.Vector3(color.r, color.g, color.b)}
    };

    var text = new THREE.Mesh( new THREE.PlaneBufferGeometry(textCanvas.width/25, textCanvas.height/25), mat );
    text.position.set( -30+5, -1, 0 );

    lineContainer.add(text);

    if (lastYearZ !== null) {
      for (var j=0; j<=10; j++) {
        var f = (j / 10)
        var tickZ = (1-f) * lastYearZ + f * z;
        yearTickGeometry.vertices.push(
          new THREE.Vector3(-18, -10, tickZ),
          new THREE.Vector3(-18 + (j===5 ? -2 : -1), -10, tickZ),
          new THREE.Vector3(-18, -10, tickZ)
        );
      }
    }
    lastYearZ = z;
  }

  for (var i=0; i<itemLanes; i++) {
    var line = this.makeLine( lineGeometry, lineMaterial );
    line.position.set( (6/5)*6*(i-(itemLanes-1)/2)-(6/5)*3,  -10,  0 );
    lines.add(line);
  }
  var line = this.makeLine( lineGeometry, lineMaterial );
  line.position.set( (6/5)*6*(i-(itemLanes-1)/2)-(6/5)*3,  -10,  0 );
  lines.add(line);

  var yearTicks = this.makeLine(yearTickGeometry, lineMaterial);
  lanes.add(yearTicks);

  lanes.add(lines);
  lanes.lines = lines;

  return lanes;
};


var terrain = new InitWebGl();

terrain.render();
